feat(reports): add instructor performance report API (#65) - #191
Open
Fury03 wants to merge 1 commit into
Open
Conversation
New GET /reports/instructors/:instructorId/performance endpoint summarising an instructor's ratings, revenue and student completion, benchmarked against platform averages. - InstructorReportQueryDto validates the optional ISO-8601 reporting period - InstructorReportService resolves the instructor (must exist and hold the INSTRUCTOR role), gathers their courses, then aggregates enrollments, per-course platform fees, review ratings and completion counts within the period - computePlatformAverages() derives rating, completion rate and per-instructor gross revenue / enrollment baselines, and compare() emits delta + percentDiff + verdict for each metric - ADMIN-guarded - spec drives the real controller->service->prisma path: instructor / role / period validation, per-course fee revenue maths, completion rate and the platform comparison
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #65
Problem Statement (The Bug)
The platform has no instructor-performance reporting.
Coursecarriesdenormalised counters (
avgRating,totalRevenue,totalEnrollments) but:an instructor's whole catalogue into one view;
Q1?";
avgRatingof 4.2 is meaninglesswithout the platform average next to it.
This can't be a local patch because the missing capability is a
cross-entity, period-bounded read model (courses × enrollments × reviews ×
platform aggregates). Reading the denormalised
Coursecolumns would bake inboth the all-time and drift problems (those counters are updated by other
code paths and can fall out of sync).
Solution Comparison and Decision
Course.*counters as-isAnalyticsEventhas no revenue or rating data and no enrollment linkage; it would require reconstructing state thatEnrollment/CourseReviewalready own.instructor_performancetableEnrollment/CourseReviewdirectly), period-aware, no new storage, comparison computed in the same request.The Change
New DTO —
InstructorReportQueryDto(from?,to?, both ISO-8601).New core method —
InstructorReportService.getReport(instructorId, dto):Revenue is computed per enrollment against its course's own
platformFeePercent, not a flat rate:compare()turns each metric into an instructor-vs-platform point:JwtAuthGuard+RolesGuard,@Roles(ADMIN)GET /reports/instructors/:instructorId/performance(new controller inReportsModule)instructorId=User.id; 404 if missing, 400 ifrole !== INSTRUCTOR; courses matched viaCourse.instructorAddress = user.stellarAddressfrom/toISO-8601;from > to→ 400; applied toEnrollment.enrolledAtandCourseReview.createdAtcourseReview.aggregate_avg.rating,_countover the instructor's courses in-periodamountPaid; fees = Σ per-courseplatformFeePercent; net = gross − feesCOMPLETEDenrollments / total enrollmentsSample response:
{ "instructor": { "id": "inst-1", "name": "Ada", "stellarAddress": "GABC" }, "period": { "from": null, "to": null }, "generatedAt": "2026-08-27T12:00:00.000Z", "metrics": { "courses": { "total": 2, "active": 1 }, "ratings": { "average": 4.5, "totalReviews": 8 }, "students": { "enrollments": 3, "completions": 2, "completionRate": 0.6667 }, "revenue": { "gross": 250, "platformFees": 45, "net": 205 } }, "platformAverages": { "instructorCount": 4, "rating": 4, "completionRate": 0.5, "grossRevenuePerInstructor": 1000, "enrollmentsPerInstructor": 20 }, "comparison": { "rating": { "instructor": 4.5, "platformAverage": 4, "delta": 0.5, "percentDiff": 12.5, "verdict": "above" }, "completionRate": { "instructor": 0.6667, "platformAverage": 0.5, "delta": 0.17, "percentDiff": 33.4, "verdict": "above" }, "grossRevenue": { "instructor": 250, "platformAverage": 1000, "delta": -750, "percentDiff": -75, "verdict": "below" }, "enrollments": { "instructor": 3, "platformAverage": 20, "delta": -17, "percentDiff": -85, "verdict": "below" } } }Compatibility Note
No
INTERFACE_VERSIONconstant exists in this repo. The change isadditive: one new route, one new DTO, one new service.
ReportsModulegains a controller/provider; the existing abuse-reportReportsController/ReportsServiceand the/reportsroutes areunchanged. No schema change, no migration.
Incidental Fixes
ReportsModulenowexportsits providers soInstructorReportServicecan be consumed elsewhere (same pattern the abuse
ReportsServiceused).rate(), per-instructor divisorinstructorCount || 1,percentDiffnull when the platform average is 0)so an instructor with no courses / an empty platform returns
0/null,never
NaN.Testing
src/modules/reports/instructor-report.service.spec.ts— resolves the realInstructorReportController+InstructorReportServicefrom a Nest module(Prisma mocked) and calls through
controller.getReport(...):404s for an unknown instructor400s when the user is not an instructor— adversarial role checkrejects an inverted reporting period— adversarialfrom > topasses the reporting period through to the enrollment querycomputes per-instructor rating, revenue (with per-course fees) and completion rate— asserts gross 250 / fees 45 (20% + 20% + 10%) / net 205,completion 0.6667
compares the instructor against platform averages— delta / verdict forrating (above) and gross revenue (below)
handles an instructor with no courses without dividing by zeroPre-existing, unrelated TypeScript failures in
src/modules/reviews/*andsrc/modules/uploads/video-transcode.service.tsexist onmainand are nottouched here.
Additional Notes
src/modules/reports/only — one DTO, one controller, oneservice, module wiring. No change to the abuse-report path, to
prisma/schema.prisma, or to any other module. Student-engagementreporting ([Backend] Create student engagement report API #64) is a separate PR.